using System;
using System.Collections.Generic;
using UnityEngine;
using SLua;

namespace Unity.LEGO.Minifig
{
    [CustomLuaClass]
    public class MinifigController : MonoBehaviour
    {
        // Constants.
        const float coyoteDelay = 0.1f;

        [Header("Movement")]
        public float maxForwardSpeed = 5f;
        [Range(4, 8)]
        public float maxBackwardSpeed = 4f;
        [Range(1, 60)]
        public float acceleration = 20.0f;
        // TODO Add sensible range when animations are fixed.
        [Range(0, 500)]
        public float maxRotateSpeed = 150f;
        [Range(0, 2500)]
        public float rotateAcceleration = 600f;
        public float jumpSpeed = 20f;
        public float gravity = 40f;

        [Header("Controls")]
        [SerializeField]
        bool inputEnabled = false;
        [SerializeField, Range(0, 10)]
        int maxJumpsInAir = 1;

        [SerializeField, HideInInspector]
        Transform leftArmTip = null;
        [SerializeField, HideInInspector]
        Transform rightArmTip = null;
        [SerializeField, HideInInspector]
        Transform leftLegTip = null;
        [SerializeField, HideInInspector]
        Transform rightLegTip = null;
        [SerializeField, HideInInspector]
        Transform head = null;

        Minifig minifig;
        CharacterController controller;
        Animator animator;
        //ClientServerManager cliSvrManager;

        bool airborne;
        float airborneTime;
        int jumpsInAir;
        Vector3 directSpeed;
        bool exploded;

        float preSpeed = 0.0f;
        float preRotateSpeed = 0.0f;
        float speed;
        float rotateSpeed;
        Vector3 moveDelta = Vector3.zero;

        int speedHash = Animator.StringToHash("Speed");
        int rotateSpeedHash = Animator.StringToHash("Rotate Speed");
        int groundedHash = Animator.StringToHash("Grounded");
        int jumpHash = Animator.StringToHash("Jump");

        Action<bool> onSpecialComplete;

        void OnValidate()
        {
            maxForwardSpeed = Mathf.Clamp(maxForwardSpeed, 5, 30);
        }

        void Awake()
        {
            minifig = GetComponent<Minifig>();
            controller = GetComponent<CharacterController>();
            animator = GetComponent<Animator>();
//            cliSvrManager = GameObject.Find("Game Manager").GetComponent<ClientServerManager>();

            // Initialise animation.
            animator.SetBool(groundedHash, true);

            // Make sure the Character Controller is grounded if starting on the ground.
            controller.Move(Vector3.down * 0.01f);
        }

        void Update()
        {
            if (exploded)
            {
                return;
            }

            // Handle input.
            if (inputEnabled)
            {
                if(Input.GetKeyDown(KeyCode.L))
                {
                   // cliSvrManager.Dead();
                    Explode();
                }

                // Calculate direct speed and speed.
                var right = Vector3.right;
                var forward = Vector3.forward;
                if (Camera.main)
                {
                    right = Camera.main.transform.right;
                    right.y = 0.0f;
                    right.Normalize();
                    forward = Camera.main.transform.forward;
                    forward.y = 0.0f;
                    forward.Normalize();
                }

                var targetSpeed = right * Input.GetAxisRaw("Horizontal");
                targetSpeed += forward * Input.GetAxisRaw("Vertical");
                if (targetSpeed.sqrMagnitude > 0.0f)
                {
                    targetSpeed.Normalize();
                }
                targetSpeed *= maxForwardSpeed;

                var speedDiff = targetSpeed - directSpeed;
                if (speedDiff.sqrMagnitude < acceleration * acceleration * Time.deltaTime * Time.deltaTime)
                {
                    directSpeed = targetSpeed;
                }
                else if (speedDiff.sqrMagnitude > 0.0f)
                {
                    speedDiff.Normalize();

                    directSpeed += speedDiff * acceleration * Time.deltaTime;
                }
                speed = directSpeed.magnitude;

                // Calculate rotation speed - ignore rotate acceleration.
                rotateSpeed = 0.0f;
                if (targetSpeed.sqrMagnitude > 0.0f)
                {
                    var localTargetSpeed = transform.InverseTransformDirection(targetSpeed);
                    var angleDiff = Vector3.SignedAngle(Vector3.forward, localTargetSpeed.normalized, Vector3.up);

                    if (angleDiff > 0.0f)
                    {
                        rotateSpeed = maxRotateSpeed;
                    }
                    else if (angleDiff < 0.0f)
                    {
                        rotateSpeed = -maxRotateSpeed;
                    }

                    // Assumes that x > NaN is false - otherwise we need to guard against Time.deltaTime being zero.
                    if (Mathf.Abs(rotateSpeed) > Mathf.Abs(angleDiff) / Time.deltaTime)
                    {
                        rotateSpeed = angleDiff / Time.deltaTime;
                    }
                }

                // Calculate move delta.
                moveDelta = new Vector3(directSpeed.x, moveDelta.y, directSpeed.z);


                // Check if player is grounded.
                if (!airborne)
                {
                    jumpsInAir = maxJumpsInAir;
                }

                // Check if player is jumping.
                if (Input.GetButtonDown("Jump"))
                {
                    if (!airborne || jumpsInAir > 0)
                    {
                        if (airborne)
                        {
                            jumpsInAir--;
                        }

                        moveDelta.y = jumpSpeed;
                        animator.SetTrigger(jumpHash);

                        airborne = true;
                        airborneTime = coyoteDelay;
                    }
                }
            }

            var wasGrounded = controller.isGrounded;

            if (!controller.isGrounded)
            {
                // Apply gravity.
                moveDelta.y -= gravity * Time.deltaTime;

                airborneTime += Time.deltaTime;
            }

            // Move minifig - check if game object was made inactive in some callback to avoid warnings from CharacterController.Move.
            if (gameObject.activeInHierarchy)
            {
                if(inputEnabled)
                {
                    float speedDif = Math.Abs(preSpeed - speed);
                    float rotateSpeedDif = Math.Abs(preRotateSpeed - rotateSpeed);
                    if(speedDif > 0.0f || rotateSpeedDif > 0.0f)
                    {
                      //  cliSvrManager.SendMoveDelta(moveDelta, speed, rotateSpeed);
                    }
                    controller.Move((moveDelta) * Time.deltaTime);
                    transform.Rotate(0, rotateSpeed * Time.deltaTime, 0);
                }
            }

            // If becoming grounded by this Move, reset y movement and airborne time.
            if (!wasGrounded && controller.isGrounded)
            {
                moveDelta.y = 0.0f;
                airborneTime = 0.0f;
            }

            // Update airborne state.
            airborne = airborneTime >= coyoteDelay;

            // Update animation - delay airborne animation slightly to avoid flailing arms when falling a short distance.
            animator.SetFloat(speedHash, speed);
            animator.SetFloat(rotateSpeedHash, rotateSpeed);
            animator.SetBool(groundedHash, !airborne);
        }

        public void SetInputEnabled(bool enabled)
        {
            inputEnabled = enabled;
        }

        public void SetMoveDelta(Vector3 delta, double speed, double rotateSpeed)
        {
            //moveDelta = delta;
            this.speed = (float)speed;
            this.rotateSpeed = (float)rotateSpeed;
        }

        public void Explode()
        {
            const float horizontalVelocityTransferRatio = 0.35f;
            const float verticalVelocityTransferRatio = 0.1f;
            const float angularVelocityTransferRatio = 1.0f;

            if (!exploded)
            {
                exploded = true;
                animator.enabled = false;
                controller.enabled = false;

                var transferredSpeed = Vector3.Scale(moveDelta, new Vector3(horizontalVelocityTransferRatio, verticalVelocityTransferRatio, horizontalVelocityTransferRatio));
                var transferredAngularSpeed = (rotateSpeed) * angularVelocityTransferRatio;

                MinifigExploder.Explode(minifig, leftArmTip, rightArmTip, leftLegTip, rightLegTip, head, transferredSpeed, transferredAngularSpeed);
            }
        }

    }

}
